Trend Following System - Apple Stock

Calculate slow and fast exponential moving averages for AAPL stock using historical data, visualize results and calculate return and performance metrics. Our strategy purchases the stock as the fast exponential moving average crosses above the slow moving average. This system does not go short.

Alex Labuda
9/9/2021

Retrieve Apple and S&P500 Historical Data

Show code
# Save apple data to a dataframe
df <- data.frame(Date=index(AAPL),coredata(AAPL))
# take most recent observations
df <- tail(df, 4000)
# reset index
rownames(df) = NULL


# Save DAL data to a dataframe
dal_df <- data.frame(Date=index(DAL),coredata(DAL))
# take most recent observations
dal <- tail(dal_df, 4000)
# reset index
rownames(dal) = NULL


# SPX to a dataframe
SPX <- data.frame(Date=index(GSPC),coredata(GSPC))
# take most recent observations
SPX <- tail(SPX, 4000)
# reset index
rownames(SPX) = NULL
Show code
# copy the data
raw_data = df
dal_df   = dal
Show code
# rename AAPL columns for simplicity
names(raw_data)[names(raw_data)=="AAPL.Open"]     = "Open"
names(raw_data)[names(raw_data)=="AAPL.High"]     = "High"
names(raw_data)[names(raw_data)=="AAPL.Low"]      = "Low"
names(raw_data)[names(raw_data)=="AAPL.Close"]    = "Close"
names(raw_data)[names(raw_data)=="AAPL.Volume"]   = "Volume"
names(raw_data)[names(raw_data)=="AAPL.Adjusted"] = "Price"

# rename DAL columns for simplicity
names(dal_df)[names(dal_df)=="DAL.Open"]     = "Open"
names(dal_df)[names(dal_df)=="DAL.High"]     = "High"
names(dal_df)[names(dal_df)=="DAL.Low"]      = "Low"
names(dal_df)[names(dal_df)=="DAL.Close"]    = "Close"
names(dal_df)[names(dal_df)=="DAL.Volume"]   = "Volume"
names(dal_df)[names(dal_df)=="DAL.Adjusted"] = "Price"

# rename SPX columns for simplicity
names(SPX)[names(SPX)=="GSPC.Open"]     = "Open"
names(SPX)[names(SPX)=="GSPC.High"]     = "High"
names(SPX)[names(SPX)=="GSPC.Low"]      = "Low"
names(SPX)[names(SPX)=="GSPC.Close"]    = "Close"
names(SPX)[names(SPX)=="GSPC.Volume"]   = "Volume"
names(SPX)[names(SPX)=="GSPC.Adjusted"] = "Price"

Sample Period

Apple
Show code
# retrieve max and min dates
date_max = max(raw_data$Date)
date_min = min(raw_data$Date)

print(paste("Our sample considers OHLC data from", date_min, "through", date_max, "for Apple, Delta and the S&P500"))
[1] "Our sample considers OHLC data from 2008-01-29 through 2023-12-15 for Apple, Delta and the S&P500"
Show code
summary(raw_data[,1:5], digits = 3)
      Date                 Open             High       
 Min.   :2008-01-29   Min.   :  2.84   Min.   :  2.93  
 1st Qu.:2012-01-16   1st Qu.: 14.64   1st Qu.: 14.92  
 Median :2016-01-06   Median : 28.30   Median : 28.57  
 Mean   :2016-01-06   Mean   : 52.91   Mean   : 53.49  
 3rd Qu.:2019-12-26   3rd Qu.: 66.26   3rd Qu.: 67.00  
 Max.   :2023-12-15   Max.   :198.02   Max.   :199.62  
      Low             Close       
 Min.   :  2.79   Min.   :  2.79  
 1st Qu.: 14.58   1st Qu.: 14.71  
 Median : 28.00   Median : 28.29  
 Mean   : 52.36   Mean   : 52.95  
 3rd Qu.: 65.64   3rd Qu.: 66.52  
 Max.   :197.00   Max.   :198.11  
Show code
sd(raw_data$Close)
[1] 54.61935
SPX
Show code
summary(SPX[,1:5]) 
      Date                 Open             High       
 Min.   :2008-01-29   Min.   : 679.3   Min.   : 695.3  
 1st Qu.:2012-01-16   1st Qu.:1354.3   1st Qu.:1362.3  
 Median :2016-01-06   Median :2088.8   Median :2096.9  
 Mean   :2016-01-06   Mean   :2351.7   Mean   :2365.2  
 3rd Qu.:2019-12-26   3rd Qu.:3003.3   3rd Qu.:3014.7  
 Max.   :2023-12-15   Max.   :4804.5   Max.   :4818.6  
      Low             Close       
 Min.   : 666.8   Min.   : 676.5  
 1st Qu.:1343.3   1st Qu.:1354.6  
 Median :2079.2   Median :2088.7  
 Mean   :2337.1   Mean   :2352.1  
 3rd Qu.:2988.3   3rd Qu.:3003.4  
 Max.   :4780.0   Max.   :4796.6  
Show code
sd(SPX$Close)
[1] 1108.915
Delta
Show code
summary(dal_df[,1:5])
      Date                 Open            High            Low       
 Min.   :2008-01-29   Min.   : 3.90   Min.   : 4.06   Min.   : 3.51  
 1st Qu.:2012-01-16   1st Qu.:11.79   1st Qu.:12.04   1st Qu.:11.57  
 Median :2016-01-06   Median :36.03   Median :36.66   Median :35.35  
 Mean   :2016-01-06   Mean   :31.77   Mean   :32.24   Mean   :31.27  
 3rd Qu.:2019-12-26   3rd Qu.:46.89   3rd Qu.:47.43   3rd Qu.:46.20  
 Max.   :2023-12-15   Max.   :63.23   Max.   :63.44   Max.   :62.38  
     Close      
 Min.   : 3.93  
 1st Qu.:11.80  
 Median :36.03  
 Mean   :31.75  
 3rd Qu.:46.76  
 Max.   :63.16  
Show code
sd(dal_df$Close)
[1] 17.40375

Calculate Daily Return

Calculate a simple day-to-day return, from adjusted-close to adjusted-close and store in return and log_return vector:

Show code
# create a blank column
raw_data$return = NA
raw_data$log_return = NA

# loop through data and calculate daily returns
for(t in 2:nrow(raw_data)){
  raw_data$return[t] = (raw_data$Price[t] - raw_data$Price[t-1])/ raw_data$Price[t-1]
  raw_data$log_return[t] = log(raw_data$Price[t]/raw_data$Price[t-1])
}


# Do the same for DAL
dal_df$return = NA
dal_df$log_return = NA

for(t in 2:nrow(dal_df)){
  dal_df$return[t] = (dal_df$Price[t] - dal_df$Price[t-1])/ dal_df$Price[t-1]
  dal_df$log_return[t] = log(dal_df$Price[t]/dal_df$Price[t-1])
}


# Do the same for SP500
SPX$return = NA
SPX$log_return = NA

for(t in 2:nrow(SPX)){
  SPX$return[t] = (SPX$Price[t] - SPX$Price[t-1])/ SPX$Price[t-1]
  SPX$log_return[t] = log(SPX$Price[t]/SPX$Price[t-1])
}


head(subset(raw_data, select = c("Date", "Close", "return", "log_return"))) |> 
  gt()
Date Close return log_return
2008-01-29 4.697857 NA NA
2008-01-30 4.720714 0.004865325 0.004853528
2008-01-31 4.834286 0.024058165 0.023773327
2008-02-01 4.776786 -0.011894262 -0.011965564
2008-02-04 4.701786 -0.015700991 -0.015825557
2008-02-05 4.620000 -0.017394658 -0.017547723
Show code
head(subset(dal_df, select = c("Date", "Close", "return", "log_return"))) |> 
  gt()
Date Close return log_return
2008-01-29 16.01 NA NA
2008-01-30 16.15 0.008744688 0.008706675
2008-01-31 16.82 0.041485849 0.040648395
2008-02-01 18.53 0.101665131 0.096822791
2008-02-04 17.25 -0.069077528 -0.071579279
2008-02-05 17.03 -0.012753526 -0.012835550
Show code
head(subset(SPX, select = c("Date", "Close", "return", "log_return"))) |> 
  gt()
Date Close return log_return
2008-01-29 1362.30 NA NA
2008-01-30 1355.81 -0.004763995 -0.004775379
2008-01-31 1378.55 0.016772254 0.016633153
2008-02-01 1395.42 0.012237492 0.012163219
2008-02-04 1380.82 -0.010462869 -0.010517990
2008-02-05 1336.64 -0.031995433 -0.032518473

Indicators and Studies

Simple Moving Averages Functions

Create a function to calculate two simple moving averages of differing lengths for use for a trend-following trading system (for this system we won’t be using a simple moving average):

Show code
Two_MovAvg_function = function(variable, slow_period = 100, fast_period = 20){
  # blank list of variables
  fast_ma = rep(NA, length(variable))
  slow_ma = rep(NA, length(variable))
  
  # loop to calculate average price for each record
  for (t in (slow_period+1):length(variable)){
    est_slow = mean(variable[(t-(slow_period+1)):(t-1)])
    est_fast = mean(variable[(t-(fast_period+1)):(t-1)])
    # update current average price
    fast_ma[t] = est_fast
    slow_ma[t] = est_slow
  }
  # create a dataframe for new vectors
  ma_data = data.frame(fast_ma = fast_ma,
                       slow_ma  = slow_ma)
  return(ma_data)
}

Exponential Moving Averages Function

Create a function to calculate two exponential moving averages of differing lengths for use for a trend-following trading system and store in fast_ma, slow_ma and ema_diff vector:

Show code
EA_Mov_Avg = function(variable, slow_lag = 100, fast_lag = 25){
  fast_ea = variable
  slow_ea = variable
  ema_diff = rep(NA, length(variable))
  
  # Loop to calculate each exponential average
  for (t in 2:length(variable)){ 
      est_slow = slow_ea[t-1] + (slow_ea[t] - slow_ea[t-1]) / ((slow_lag + 1) / 2)
      est_fast = fast_ea[t-1] + (fast_ea[t] - fast_ea[t-1]) / ((fast_lag + 1) / 2)
      
      ema_diff[t] = est_slow - est_fast
      
    slow_ea[t] = est_slow
    fast_ea[t] = est_fast
  }
  # dataframe for new vector
  ema_data = data.frame(fast_ema = fast_ea,
                        slow_ema = slow_ea,
                        ema_diff = ema_diff)
  return(ema_data)
}
Show code
# add these as new vectors to original data 

new_df = EA_Mov_Avg(raw_data$Close, slow_lag = 150, fast_lag = 25)
new_dal_df = EA_Mov_Avg(dal_df$Close, slow_lag = 150, fast_lag = 25)

# Apple
raw_data$slow_ma = new_df$slow_ema
raw_data$fast_ma = new_df$fast_ema
raw_data$ema_diff = new_df$ema_diff

# DAL
dal_df$slow_ma = new_dal_df$slow_ema
dal_df$fast_ma = new_dal_df$fast_ema
dal_df$ema_diff = new_dal_df$ema_diff


tail(raw_data)
           Date   Open   High    Low  Close    Volume  Price
3995 2023-12-08 194.20 195.99 193.67 195.71  53377300 195.71
3996 2023-12-11 193.11 193.49 191.42 193.18  60943700 193.18
3997 2023-12-12 193.08 194.72 191.72 194.71  52696900 194.71
3998 2023-12-13 195.09 198.00 194.85 197.96  70404200 197.96
3999 2023-12-14 198.02 199.62 196.16 198.11  66831600 198.11
4000 2023-12-15 197.53 198.40 197.00 197.57 128256700 197.57
           return    log_return  slow_ma  fast_ma   ema_diff
3995  0.007412377  0.0073850400 178.4263 188.2086  -9.782240
3996 -0.012927362 -0.0130116472 178.6217 188.5910  -9.969244
3997  0.007920148  0.0078889478 178.8348 189.0617 -10.226848
3998  0.016691489  0.0165537174 179.0881 189.7462 -10.658021
3999  0.000757698  0.0007574111 179.3401 190.3895 -11.049447
4000 -0.002725725 -0.0027294461 179.5816 190.9419 -11.360335
Show code
tail(dal_df)
           Date  Open  High   Low Close   Volume Price        return
3995 2023-12-08 40.26 40.74 39.86 40.35  8723000 40.35  0.0002478513
3996 2023-12-11 40.41 40.63 40.11 40.51  7295400 40.51  0.0039653000
3997 2023-12-12 40.52 41.46 40.52 41.24  8448900 41.24  0.0180203255
3998 2023-12-13 40.87 41.36 40.12 41.19 12821300 41.19 -0.0012124891
3999 2023-12-14 41.53 42.70 41.49 42.44 14530800 42.44  0.0303471727
4000 2023-12-15 42.59 42.59 41.98 42.34 12902100 42.34 -0.0023562318
        log_return  slow_ma  fast_ma    ema_diff
3995  0.0002478206 37.69339 36.66494  1.02844868
3996  0.0039574589 37.73070 36.96072  0.76998124
3997  0.0178598841 37.77718 37.28989  0.48728631
3998 -0.0012132247 37.82238 37.58990  0.23248104
3999  0.0298958063 37.88354 37.96298 -0.07944294
4000 -0.0023590121 37.94257 38.29968 -0.35711049

Average True Range

True Range

First calculate the maximum of the aforementioned ranges and store in max_range vector:

Show code
max_range_functon = function(variable){
  max_range = rep(0, length(variable))
  
  for (t in 2:length(variable$Price)){
    max_rng = max(abs(variable$High[t] - variable$Low[t]), abs(variable$High[t] - variable$Close[t-1]), abs(variable$Close[t-1] - variable$Low[t]))
    
    max_range[t] = max_rng
  }
  max_range_data = data.frame(max_range = max_range)
  
  return(max_range_data)
}

# Test Function


# Implement on full APPL dataset
max_range_df = max_range_functon(raw_data)
# Implement on full DAL dataset
max_range_dal_df = max_range_functon(dal_df)


# Create new variable in data
raw_data$max_range = max_range_df$max_range
dal_df$max_range = max_range_dal_df$max_range

subset(raw_data[25:32,], select = c("Date", "Open", "High", "Low", "Close", "max_range"))
         Date     Open     High      Low    Close max_range
25 2008-03-04 4.356786 4.460000 4.300000 4.450714 0.1599998
26 2008-03-05 4.413571 4.469286 4.366071 4.446071 0.1032147
27 2008-03-06 4.450357 4.553571 4.314643 4.318929 0.2389283
28 2008-03-07 4.300357 4.392143 4.251786 4.366071 0.1403565
29 2008-03-10 4.356429 4.409286 4.263214 4.274643 0.1460719
30 2008-03-11 4.432143 4.552857 4.357143 4.548214 0.2782140
31 2008-03-12 4.537143 4.595714 4.470357 4.501071 0.1253572
32 2008-03-13 4.432143 4.625000 4.392857 4.569286 0.2321429
Show code
subset(dal_df[25:32,], select = c("Date", "Open", "High", "Low", "Close", "max_range"))
         Date  Open  High   Low Close max_range
25 2008-03-04 12.85 13.53 12.51 13.14  1.020000
26 2008-03-05 13.19 14.60 12.87 14.33  1.730000
27 2008-03-06 14.15 14.45 13.34 13.50  1.110000
28 2008-03-07 13.13 13.75 12.71 12.89  1.040000
29 2008-03-10 12.92 13.26 11.95 11.98  1.310000
30 2008-03-11 12.16 12.64 11.64 12.11  1.000000
31 2008-03-12 11.58 11.80 10.05 10.13  2.059999
32 2008-03-13  9.82 10.72  9.56 10.52  1.160000

Average True Range Function

Create function that uses the true range vector to calculate a moving average of the true range with a user-specified lag period and store in atr vector:

Show code
ATR_function = function(variable, lag = 15){
  atr_list = rep(NA, length(variable))
  
  # Loop to create ATR
  for (t in (lag+2):length(variable)){
    est_atr = mean(variable[(t-(lag)):t])
    
    atr_list[t] = est_atr
  }
  
  atr_data = data.frame(atr = atr_list)
  return(atr_data)
}
# Add vector to our data
## AAPL
atr_data = ATR_function(raw_data$max_range)
raw_data$atr = atr_data$atr

## DAL
atr_dal_data = ATR_function(dal_df$max_range)
dal_df$atr = atr_dal_data$atr

Risk per lot

This is a multiple of the ATR for volatility-based position sizing:

Store in risk_per_lot vector:

Show code
risk_multiplier = 5
equity = 100000


raw_data$risk_per_lot = raw_data$atr * risk_multiplier
subset(raw_data[200:210,], select = c("Close", "max_range", "atr", "risk_per_lot")) 
       Close max_range       atr risk_per_lot
200 3.424286 0.2107141 0.2431697    1.2158484
201 3.384643 0.1753569 0.2398661    1.1993305
202 3.218571 0.1700001 0.2342411    1.1712056
203 3.444286 0.3721430 0.2357144    1.1785718
204 3.222857 0.2300000 0.2336831    1.1684156
205 3.147857 0.1174998 0.2229019    1.1145093
206 3.211071 0.1475000 0.2192411    1.0962056
207 3.081786 0.1917851 0.2124554    1.0622769
208 2.874643 0.2303572 0.2053571    1.0267857
209 2.949286 0.1778572 0.1994196    0.9970982
210 3.319643 0.4360709 0.2135045    1.0675223
Show code
dal_df$risk_per_lot = dal_df$atr * risk_multiplier
subset(dal_df[200:210,], select = c("Close", "max_range", "atr", "risk_per_lot")) 
    Close max_range      atr risk_per_lot
200  8.98 0.8299999 1.316250     6.581250
201  8.84 0.6099997 1.303125     6.515625
202  7.37 1.7300000 1.349375     6.746875
203  8.17 0.9800000 1.281875     6.409376
204  7.85 0.6999998 1.232500     6.162500
205  7.87 0.7000003 1.178125     5.890626
206  7.88 0.8799996 1.150000     5.750000
207  7.00 1.0400004 1.138750     5.693751
208  7.02 1.1199999 1.163750     5.818750
209  6.82 1.1900001 1.140625     5.703125
210  7.35 0.9099998 1.041250     5.206250
Show code
# save moving average cross-over dates for chart annotations

h = raw_data[which((raw_data$fast_ma > raw_data$slow_ma) & (raw_data$fast_ma[-1] < raw_data$slow_ma[-1]))+1,]
l = raw_data[which((raw_data$fast_ma < raw_data$slow_ma) & (raw_data$fast_ma[-1] > raw_data$slow_ma[-1]))+1,]
h = na.exclude(h)
h
           Date       Open       High        Low      Close
161  2008-09-16   4.780714   5.089286   4.719643   4.995714
1208 2012-11-12  19.791071  19.803572  19.237499  19.386786
1897 2015-08-10  29.132500  29.997499  29.132500  29.930000
2071 2016-04-19  26.969999  27.000000  26.557501  26.727501
2726 2018-11-21  44.932499  45.067501  44.137501  44.195000
2857 2019-06-04  43.860001  44.957500  43.630001  44.910000
3063 2020-03-27  63.187500  63.967499  61.762501  61.935001
3598 2022-05-11 153.500000 155.449997 145.809998 146.500000
3693 2022-09-27 152.740005 154.720001 149.949997 151.759995
3967 2023-10-30 169.020004 171.169998 168.869995 170.289993
         Volume      Price       return   log_return    slow_ma
161  1199836400   4.234765 -0.003419769 -0.003425630   5.747746
1208  515802000  16.579727 -0.007732053 -0.007762100  21.457792
1897  219806400  27.131924  0.036357311  0.035711979  30.521466
2071  129539600  24.464561 -0.005303275 -0.005317388  26.907706
2726  124496800  42.418842 -0.001130032 -0.001130671  50.357920
2857  123872000  43.456684  0.036583821  0.035930519  46.671909
3063  204216800  60.482265 -0.041402250 -0.042283739  66.466317
3598  142689800 145.242661 -0.051841208 -0.053233289 161.335502
3693   84442700 150.666519  0.006566277  0.006544813 156.180428
3967   51131000 170.065933  0.012305221  0.012230127 174.363520
        fast_ma     ema_diff max_range       atr risk_per_lot
161    5.722272 0.0254730475 0.3696427 0.2368751     1.184375
1208  21.451680 0.0061117641 0.5660725 0.6715622     3.357811
1897  30.497427 0.0240385186 1.1175003 0.7928125     3.964062
2071  26.907096 0.0006101968 0.4424992 0.5092187     2.546093
2726  50.248957 0.1089631330 0.9300003 1.8137500     9.068750
2857  46.601018 0.0708910523 1.6324997 1.3182809     6.591405
3063  66.384702 0.0816144377 2.8474998 4.9378126    24.689063
3598 160.683833 0.6516691677 9.6399994 6.2474985    31.237493
3693 156.138959 0.0414687757 4.7700043 4.6318750    23.159375
3967 174.234966 0.1285538854 2.9499969 3.0418768    15.209384
Show code
l
           Date       Open       High        Low      Close
40   2008-03-26   5.031071   5.205000   5.022857   5.180714
304  2009-04-13   4.286071   4.320714   4.250000   4.293571
1399 2013-08-16  17.862499  17.962143  17.816429  17.940357
2069 2016-04-15  28.027500  28.075001  27.432501  27.462500
2148 2016-08-08  26.879999  27.092501  26.790001  27.092501
2809 2019-03-26  47.915001  48.220001  46.145000  46.697498
2861 2019-06-10  47.952499  48.842499  47.904999  48.145000
3076 2020-04-16  71.845001  72.050003  70.587502  71.672501
3656 2022-08-04 166.009995 167.190002 164.429993 165.809998
3790 2023-02-15 153.110001 155.500000 152.880005 155.330002
3971 2023-11-03 174.240005 176.820007 173.350006 176.649994
         Volume      Price       return   log_return    slow_ma
40   1182084400   4.391588  0.028940012  0.028529158   4.638723
304   389236400   3.639573  0.005435969  0.005421247   3.826802
1399  362306000  15.636861  0.008876784  0.008837617  16.415864
2069  187756000  25.137333 -0.020071315 -0.020275480  26.910664
2148  112148800  25.084808  0.008280810  0.008246712  25.439263
2809  199202000  45.012993 -0.010331733 -0.010385476  45.161955
2861  104883600  46.586994  0.012779220  0.012698255  46.684806
3076  157125200  69.991371  0.007945865  0.007914463  66.394240
3656   55474100 164.386932 -0.001926246 -0.001928103 153.529224
3790   65573800 154.702438  0.013903323  0.013807558 146.176338
3971   79763700 176.417572 -0.005181159 -0.005194628 174.384907
        fast_ma      ema_diff max_range       atr risk_per_lot
40     4.641450 -0.0027265677 0.1821427 0.1907812    0.9539059
304    3.838849 -0.0120475123 0.0707140 0.1428793    0.7143966
1399  16.450733 -0.0348689245 0.1796436 0.3495760    1.7478800
2069  26.926401 -0.0157370506 0.6424999 0.4750001    2.3750007
2148  25.440247 -0.0009847918 0.3024998 0.4473438    2.2367191
2809  45.256133 -0.0941787961 2.0750008 1.0417192    5.2085960
2861  46.708433 -0.0236272637 1.3050003 1.2028127    6.0140634
3076  66.711820 -0.3175799840 1.4625015 2.6524994   13.2624972
3656 153.556168 -0.0269442059 2.7600098 3.9068747   19.5343733
3790 146.585439 -0.4091013714 2.6199951 3.9224977   19.6124887
3971 174.430541 -0.0456339127 4.2200012 3.3275013   16.6375065
Show code
# save moving average cross-over dates for chart annotations

hi = dal_df[which((dal_df$fast_ma > dal_df$slow_ma) & (dal_df$fast_ma[-1] < dal_df$slow_ma[-1]))+1,]
lo = dal_df[which((dal_df$fast_ma < dal_df$slow_ma) & (dal_df$fast_ma[-1] > dal_df$slow_ma[-1]))+1,]
hi = na.exclude(hi)
hi
           Date  Open  High   Low Close   Volume     Price
24   2008-03-03 13.27 13.35 12.50 12.98  4713200 11.473167
204  2008-11-14  8.09  8.46  7.76  7.85  8104200  6.938702
258  2009-02-04  7.00  7.12  6.50  6.51 11712200  5.754261
624  2010-07-20 11.18 11.62 11.10 11.53  9408900 10.191497
753  2011-01-21 11.71 11.78 11.39 11.54 15559900 10.200336
1138 2012-08-01  9.49  9.62  9.40  9.48 15043400  8.379478
1690 2014-10-13 33.30 33.31 30.12 30.90 41248000 27.618721
1853 2015-06-08 42.37 42.62 40.65 40.75 18359700 36.649052
2015 2016-01-28 44.84 45.00 42.52 43.20 13996900 39.069405
2074 2016-04-22 43.43 44.67 43.16 44.62 14258600 40.481609
2318 2017-04-11 45.00 45.32 44.49 45.29 10506800 41.805252
2413 2017-08-25 45.43 47.06 45.35 46.68 13938800 43.545128
2589 2018-05-09 52.17 52.28 51.46 51.62  8403900 48.732548
2623 2018-06-27 51.03 51.18 49.70 49.89  9035800 47.379257
2702 2018-10-18 54.29 54.30 52.96 53.13  5871200 50.800251
2746 2018-12-21 50.51 51.28 49.29 49.45 11680200 47.578712
2946 2019-10-09 54.01 54.40 53.59 53.92  7615300 52.895596
2986 2019-12-05 56.39 56.41 55.70 55.88  3899900 55.225609
3041 2020-02-26 51.44 51.77 49.00 49.59 15985900 49.347950
3391 2021-07-16 41.83 42.05 39.84 40.06 16726600 39.864468
3456 2021-10-18 40.67 41.41 40.52 41.01 10140200 40.809826
3550 2022-03-03 38.49 38.74 36.38 36.56 17589100 36.381550
3607 2022-05-24 38.82 38.95 36.78 37.22 10856500 37.038326
3615 2022-06-06 39.00 39.29 38.31 39.00 12330400 38.809635
3752 2022-12-20 32.86 33.28 32.75 32.90  5995900 32.739414
3816 2023-03-24 31.74 31.86 31.04 31.59 12647300 31.435808
3944 2023-09-27 36.77 37.15 36.53 36.66  7996900 36.557686
            return    log_return   slow_ma   fast_ma    ema_diff
24   -0.0277155297 -0.0281068524 16.137392 15.942950 0.194442154
204  -0.0391678329 -0.0399555292  8.792714  8.790646 0.002067308
258  -0.0565219322 -0.0581821600  9.314662  9.281247 0.033415222
624   0.0131811903  0.0130950743 12.138142 12.121677 0.016464694
753  -0.0060291583 -0.0060474071 12.441692 12.429321 0.012371028
1138 -0.0176167825 -0.0177738049 10.136524 10.119460 0.017063860
1690 -0.0610758691 -0.0630206008 36.256417 36.147118 0.109298469
1853 -0.0501165735 -0.0514160109 43.967611 43.864808 0.102802775
2015 -0.0339892682 -0.0345803353 47.192733 46.987755 0.204978627
2074 -0.0077830791 -0.0078135253 47.098074 47.017579 0.080495053
2318  0.0071159814  0.0070907823 46.402673 46.363002 0.039671307
2413  0.0325150466  0.0319976188 49.272102 49.117258 0.154844325
2589 -0.0143210699 -0.0144246061 53.213014 53.148667 0.064346206
2623 -0.0231056402 -0.0233767599 53.365226 53.318601 0.046625021
2702 -0.0215469382 -0.0217824628 54.346382 54.271469 0.074913582
2746 -0.0286781382 -0.0290973910 54.838522 54.693107 0.145415298
2946  0.0135337002  0.0134429376 56.682551 56.506699 0.175852033
2986 -0.0032108907 -0.0032160566 56.330176 56.322690 0.007486750
3041 -0.0255452344 -0.0258771791 57.180516 56.932703 0.247812371
3391 -0.0311969922 -0.0316939821 43.547324 43.329234 0.218090202
3456  0.0004878033  0.0004876843 42.211823 42.203141 0.008681967
3550 -0.0389063741 -0.0396834493 40.603786 40.411866 0.191920582
3607 -0.0581984495 -0.0599606948 39.856313 39.740031 0.116281668
3615  0.0119355868  0.0118649194 39.874651 39.841234 0.033417579
3752 -0.0006073834 -0.0006075679 33.944100 33.929391 0.014709243
3816 -0.0171126373 -0.0172607507 35.623545 35.506664 0.116881284
3944 -0.0029915316 -0.0029960152 40.151455 39.992967 0.158487997
     max_range       atr risk_per_lot
24   0.8500004 0.9524999     4.762499
204  0.6999998 1.2325001     6.162500
258  0.6199999 1.1000001     5.500000
624  0.5199995 0.6918748     3.459374
753  0.3899994 0.4356251     2.178125
1138 0.2500000 0.4187499     2.093750
1690 3.1900005 1.4331254     7.165627
1853 2.2500000 1.4418755     7.209377
2015 2.4799995 2.0868747    10.434374
2074 1.8100014 1.3693745     6.846873
2318 0.8299980 0.9218750     4.609375
2413 1.8500023 1.0218759     5.109379
2589 0.9099998 1.2231250     6.115625
2623 1.4799995 1.2256248     6.128124
2702 1.3400002 1.4993751     7.496876
2746 1.9899979 1.8862500     9.431250
2946 1.2000008 1.3774996     6.887498
2986 0.7099991 0.9981256     4.990628
3041 2.7700005 1.7006252     8.503126
3391 2.2099991 1.3481250     6.740625
3456 0.8899994 1.3131254     6.565627
3550 2.3600006 1.9406245     9.703122
3607 2.7400017 1.9162505     9.581252
3615 0.9799995 1.8456254     9.228127
3752 0.5299988 1.1831257     5.915629
3816 1.0999985 1.3850003     6.925002
3944 0.6200027 0.9112499     4.556249
Show code
lo = na.exclude(lo)
lo
           Date  Open  High   Low Close   Volume     Price
196  2008-11-04 11.08 12.00 10.03 11.28 24872000  9.970520
220  2008-12-09 10.78 11.64 10.53 10.87 18926300  9.608115
400  2009-08-27  7.48  7.50  7.25  7.49 10430300  6.620495
690  2010-10-21 13.03 13.54 12.90 13.53 20095600 11.959318
1004 2012-01-20  9.30  9.50  9.25  9.41  7844600  8.317604
1195 2012-10-22 10.00 10.20  9.90 10.14  7317200  8.962861
1700 2014-10-27 39.38 40.10 39.20 39.75 18958800 35.528942
1889 2015-07-29 44.81 45.33 43.85 44.18 10355300 39.733871
2039 2016-03-03 48.59 49.01 48.35 48.79  8853700 44.264862
2210 2016-11-03 42.06 42.57 41.77 41.92  8142800 38.364182
2334 2017-05-04 48.46 48.80 48.20 48.64  7467400 44.897480
2442 2017-10-06 51.40 52.52 51.40 52.01  6735400 48.517170
2600 2018-05-24 53.53 54.57 53.53 54.40  5251200 51.662292
2651 2018-08-07 54.60 55.09 54.52 54.70  4425300 52.301399
2714 2018-11-05 56.02 56.38 55.55 56.05  5206000 53.592197
2817 2019-04-05 57.44 58.20 57.33 57.73  6918300 55.933582
2981 2019-11-27 57.53 57.58 56.68 57.07  4062500 56.401676
2993 2019-12-16 57.17 58.49 57.15 58.42  7460700 57.735863
3223 2020-11-12 34.09 35.55 34.02 34.38 21676300 34.212189
3447 2021-10-05 44.99 45.56 44.46 44.74  9596000 44.521622
3538 2022-02-14 42.38 43.25 41.71 41.97 11581600 41.765141
3584 2022-04-21 44.81 46.27 44.54 44.73 31849600 44.511669
3610 2022-05-27 41.00 42.23 41.00 42.23 10730600 42.023872
3738 2022-11-30 34.51 35.38 34.21 35.37  7404100 35.197357
3764 2023-01-09 36.54 37.42 36.35 36.77 11251300 36.590527
3863 2023-06-01 36.42 36.81 36.00 36.38  7769000 36.202427
3999 2023-12-14 41.53 42.70 41.49 42.44 14530800 42.439999
            return    log_return   slow_ma   fast_ma     ema_diff
196  -0.0208333724 -0.0210534491  8.791047  8.890360 -0.099312565
220  -0.0136115943 -0.0137050813  8.747561  8.871540 -0.123978743
400   0.0040214562  0.0040133917  6.900195  6.930079 -0.029884534
690   0.0431764829  0.0422703687 11.713393 11.730301 -0.016907856
1004  0.0085748591  0.0085383038  8.547007  8.562098 -0.015091284
1195  0.0140002460  0.0139031478  9.778188  9.798855 -0.020667766
1700  0.0078602438  0.0078295130 36.246599 36.426752 -0.180152894
1889 -0.0162545673 -0.0163881220 43.440206 43.444856 -0.004649746
2039  0.0049433218  0.0049311437 46.839241 46.900670 -0.061428635
2210 -0.0002384849 -0.0002385133 40.537933 40.600753 -0.062820548
2334  0.0068307676  0.0068075436 46.334027 46.377038 -0.043010701
2442  0.0063852145  0.0063649154 49.020699 49.140316 -0.119616838
2600  0.0187265557  0.0185533725 53.202877 53.215520 -0.012642347
2651  0.0051451150  0.0051319242 52.954865 52.959755 -0.004889844
2714 -0.0012475099 -0.0012482887 54.339901 54.442930 -0.103028884
2817  0.0089130214  0.0088735348 52.148067 52.183860 -0.035793478
2981 -0.0067872694 -0.0068104076 56.331252 56.349205 -0.017952896
2993  0.0297901461  0.0293550399 56.332870 56.381922 -0.049052243
3223 -0.0182752442 -0.0184442993 32.438902 32.526346 -0.087443534
3447 -0.0048932230 -0.0049052340 42.186628 42.191977 -0.005349117
3538 -0.0085046783 -0.0085410494 40.566451 40.641506 -0.075054663
3584  0.0273311264  0.0269643000 39.631573 39.784479 -0.152905831
3610  0.0342884901  0.0337137412 39.884276 39.934239 -0.049963328
3738  0.0219589725  0.0217213466 33.897592 33.929432 -0.031840072
3764  0.0205385795  0.0203305071 33.925796 34.025409 -0.099612477
3863  0.0013762495  0.0013753033 35.093709 35.099561 -0.005852164
3999  0.0303471727  0.0298958063 37.883542 37.962985 -0.079442944
     max_range       atr risk_per_lot
196  1.9700003 1.3193751     6.596875
220  1.1100006 1.0050000     5.025000
400  0.2500000 0.3500000     1.750000
690  0.6400003 0.4850000     2.425000
1004 0.2500000 0.3181249     1.590625
1195 0.3000002 0.3274999     1.637499
1700 0.8999977 1.8275003     9.137502
1889 1.4800034 1.2612500     6.306250
2039 0.6599998 1.2812495     6.406248
2210 0.7999992 1.1793752     5.896876
2334 0.5999985 1.0999992     5.499996
2442 1.1199989 1.0718753     5.359377
2600 1.1699982 1.0900004     5.450002
2651 0.6700020 1.1387506     5.693753
2714 0.8300018 1.6181254     8.090627
2817 0.9799995 1.2262497     6.131248
2981 0.9000015 0.8443754     4.221877
2993 1.7600021 1.0968750     5.484375
3223 1.5299988 1.9068747     9.534373
3447 1.1000023 1.3125007     6.562504
3538 1.5400009 1.6268749     8.134375
3584 2.7299995 1.4668751     7.334375
3610 1.3999977 1.9500005     9.750003
3738 1.1700020 1.0193758     5.096879
3764 1.3899994 1.0831250     5.415625
3863 0.8100014 1.0481250     5.240625
3999 1.5100021 1.0112507     5.056254
Show code
# Create arrow annotations for trade entry signal

# AAPL
# Upper annotations
h_a <- list(
  x = h$Date,
  y = h$slow_ma,
  text = "Sell",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "red",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 1.5,
  opacity = 0.75,
  align = "left",
  ax = 5,
  ay = -55
)

# Lower annotations

l_a <- list(
  x = l$Date,
  y = l$slow_ma,
  text = "Buy",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 1.5,
  opacity = 0.75,
  align = "right",
  ax = -5,
  ay = 55
)


# figure labels
f <- list(
  family = "Courier New, monospace",
  size = 18,
  color = "#7f7f7f ")
x <- list(
  title = "x Axis",
  titlefont = f)
y <- list(
  title = "y Axis",
  titlefont = f)


# DAL

# Upper annotations
h_a_dal <- list(
  x = hi$Date,
  y = hi$slow_ma,
  text = "Sell",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "red",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 1.5,
  opacity = 0.75,
  align = "left",
  ax = 5,
  ay = -55
)

# Lower annotations
l_a_dal <- list(
  x = lo$Date,
  y = lo$slow_ma,
  text = "Buy",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 1.5,
  opacity = 0.75,
  align = "right",
  ax = -5,
  ay = 55
)


# figure labels
f <- list(
  family = "Courier New, monospace",
  size = 18,
  color = "#7f7f7f ")
x <- list(
  title = "x Axis",
  titlefont = f)
y <- list(
  title = "y Axis",
  titlefont = f)

Candlestick Chart

Visualizing the Trend-following System for AAPL

Show code
raw_data$MACD_direction = rep(NA, length(raw_data$Date))


# colors column for increasing and decreasing
for (i in 2:length(raw_data[,1])) {
  if (raw_data$ema_diff[i] > 0) {
      raw_data$MACD_direction[i] = 'Increasing'
  } else {
      raw_data$MACD_direction[i] = 'Decreasing'
  }
}

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing


# Plot main data
fig <- raw_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)
# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))


# plot MACD line subplot
fig2 <- raw_data 
fig2 <- fig2 %>% plot_ly(x=~Date, y=~ema_diff, type='bar', name = "MACD",
                         color = ~MACD_direction, colors = c("red", "green"), alpha = 0.8)
fig2 <- fig2 %>% layout(yaxis = list(title = "MACD"))

# use subplot to add plots to the same figure
fig = subplot(fig, fig2, heights = c(0.8,0.15), nrows = 2,
              shareX = TRUE, titleY = TRUE)

# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)
fig <- fig %>% layout(annotations = l_a)

# Add title
fig <- fig %>% layout(title = "<b> AAPL - Trend Following System </b>",
                       xaxis = list(rangeslider = list(visible = F)))

fig

Visualizing the Trend-following System for DAL

Show code
dal_df$MACD_direction = rep(NA, length(dal_df$Date))


# colors column for increasing and decreasing
for (i in 2:length(dal_df[,1])) {
  if (dal_df$ema_diff[i] > 0) {
      dal_df$MACD_direction[i] = 'Increasing'
  } else {
      dal_df$MACD_direction[i] = 'Decreasing'
  }
}

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing


# Plot main data
fig3 <- dal_df %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "DAL",
          increasing = i, decreasing = d)

# Add Fast and Slow moving average lines
fig3 <- fig3 %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig3 <- fig3 %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)
# Add y-axis title
fig3 <- fig3 %>% layout(yaxis = list(title = "Price"))


# plot MACD line subplot
fig4 <- dal_df 
fig4 <- fig4 %>% plot_ly(x=~Date, y=~ema_diff, type='bar', name = "MACD",
                         color = ~MACD_direction, colors = c("red", "green"), alpha = 0.8)
fig4 <- fig4 %>% layout(yaxis = list(title = "MACD"))

# use subplot to add plots to the same figure
fig3 = subplot(fig3, fig4, heights = c(0.8,0.15), nrows = 2,
              shareX = TRUE, titleY = TRUE)

# Add arrow annotations
fig3 <- fig3 %>% layout(annotations = h_a_dal)
fig3 <- fig3 %>% layout(annotations = l_a_dal)

# Add title
fig3 <- fig3 %>% layout(title = "<b> DAL Stock - Moving Average Crossover </b>",
                       xaxis = list(rangeslider = list(visible = F)))

fig3

Generate signals using MA Indicator

Apple Stock

Now we use our indicator to generate trading signals and store them in signal vector:

Show code
raw_data$signal = NA

# Loop through data to create signal column
for(t in 100:nrow(raw_data)-1){
  
  current_slow = raw_data$slow_ma[t]
  current_fast = raw_data$fast_ma[t]
  
  prev_slow = raw_data$slow_ma[t-1]
  prev_fast = raw_data$fast_ma[t-1]
  
  # use 'if' statement to translate crossover into signals
  if(current_fast > current_slow & prev_fast < prev_slow) {signal = "B"} else 
    if(current_fast < current_slow & prev_fast > prev_slow) {signal = "S"} else
      {signal = "H"}
  
  raw_data$signal[t+1] = signal
}

subset(raw_data[200:210,], select = c("Date","Close", "slow_ma", "fast_ma", "signal"))
          Date    Close  slow_ma  fast_ma signal
200 2008-11-10 3.424286 4.944619 3.739298      H
201 2008-11-11 3.384643 4.923957 3.712016      H
202 2008-11-12 3.218571 4.901369 3.674059      H
203 2008-11-13 3.444286 4.882070 3.656384      H
204 2008-11-14 3.222857 4.860094 3.623036      H
205 2008-11-17 3.147857 4.837415 3.586484      H
206 2008-11-18 3.211071 4.815874 3.557606      H
207 2008-11-19 3.081786 4.792906 3.521004      H
208 2008-11-20 2.874643 4.767498 3.471284      H
209 2008-11-21 2.949286 4.743416 3.431130      H
210 2008-11-24 3.319643 4.724558 3.422555      H

DAL Stock

Show code
dal_df$signal = NA

# Loop through data to create signal column
for(t in 100:nrow(dal_df)-1){
  
  current_slow = dal_df$slow_ma[t]
  current_fast = dal_df$fast_ma[t]
  
  prev_slow = dal_df$slow_ma[t-1]
  prev_fast = dal_df$fast_ma[t-1]
  
  # use 'if' statement to translate crossover into signals
  if(current_fast > current_slow & prev_fast < prev_slow) {signal = "B"} else 
    if(current_fast < current_slow & prev_fast > prev_slow) {signal = "S"} else
      {signal = "H"}
  
  dal_df$signal[t+1] = signal
}

subset(dal_df[200:210,], select = c("Date","Close", "slow_ma", "fast_ma", "signal"))
          Date Close  slow_ma  fast_ma signal
200 2008-11-10  8.98 8.833187 9.075149      H
201 2008-11-11  8.84 8.833277 9.057060      H
202 2008-11-12  7.37 8.813896 8.927286      H
203 2008-11-13  8.17 8.805368 8.869034      H
204 2008-11-14  7.85 8.792714 8.790646      H
205 2008-11-17  7.87 8.780492 8.719828      S
206 2008-11-18  7.88 8.768565 8.655225      H
207 2008-11-19  7.00 8.745141 8.527900      H
208 2008-11-20  7.02 8.722291 8.411908      H
209 2008-11-21  6.82 8.697095 8.289454      H
210 2008-11-24  7.35 8.679253 8.217188      H

Holding Status and Investment Return

APPLE Stock

After generating the signals, we must calculate the return from our strategy:

Store in investment_return vector

Show code
# used to calculate fill price penalty / skid
# we will penalize by 40% from the open - high for buy orders
# and 40% from the open - low for sell orders

buy_weight = .35
sell_weight = -.35
Show code
# Generate a holding status column  
# and investment return column

raw_data$holding = 0      # first record, you are not holding
raw_data$Inv_return = NA  # obviously the return will also be 0

# we can begin the loop
for (t in 100:nrow(raw_data)){
  
  if(t==1){prev_holding=0} 
  else {prev_holding=raw_data$holding[t-1]}
  
  # look at the signal to decide the change of holding status
  signal = raw_data$signal[t]
  if(signal=="B"){raw_data$holding[t]=1} else 
    if (signal=="S") {raw_data$holding[t]=0} else 
      if (signal=="H"){raw_data$holding[t]=prev_holding}
  
  # Now calculate investment return
  Open = raw_data$Open[t]
  Close = raw_data$Close[t]
  High = raw_data$High[t]
  Low = raw_data$Low[t]
  
  if(prev_holding==0 & signal=="B"){investment_return=Close/((High-Open)*buy_weight+Open) - 1} else 
    if(prev_holding==0 & signal=="H"){investment_return=0} else 
      #if(prev_holding==1 & signal=="S"){investment_return=Close[t-1]/(Open*(1+sell_weight)) - 1} else
      #if(prev_holding==0 & signal=="S"){investment_return=(-1)*(Close/(Open*(1+sell_weight)) - 1)} else
        if(prev_holding==1 & signal=="B"){investment_return=Close/raw_data$Close[t-1]-1} else
          if(prev_holding==1 & signal=="H"){investment_return=Close/raw_data$Close[t-1]-1} else
            {investment_return = Open/raw_data$Close[t-1]-1}
 
   # save the investment return to the dataset
  raw_data$Inv_return[t] = investment_return}

DAL Stock

Show code
# Generate a holding status column  
# and investment return column

dal_df$holding = 0      # first record, you are not holding
dal_df$Inv_return = NA  # obviously the return will also be 0

# we can begin the loop
for (t in 100:nrow(dal_df)){
  
  if(t==1){prev_holding=0} 
  else {prev_holding=dal_df$holding[t-1]}
  
  # look at the signal to decide the change of holding status
  signal = dal_df$signal[t]
  if(signal=="B"){dal_df$holding[t]=1} else 
    if (signal=="S") {dal_df$holding[t]=0} else 
      if (signal=="H"){dal_df$holding[t]=prev_holding}
  
  # Now calculate investment return
  Open = dal_df$Open[t]
  Close = dal_df$Close[t]
  High = dal_df$High[t]
  Low = dal_df$Low[t]
  
  if(prev_holding==0 & signal=="B"){investment_return=Close/((High-Open)*buy_weight+Open) - 1} else 
    if(prev_holding==0 & signal=="H"){investment_return=0} else 
      #if(prev_holding==1 & signal=="S"){investment_return=Close[t-1]/(Open*(1+sell_weight)) - 1} else
      #if(prev_holding==0 & signal=="S"){investment_return=(-1)*(Close/(Open*(1+sell_weight)) - 1)} else
        if(prev_holding==1 & signal=="B"){investment_return=Close/dal_df$Close[t-1]-1} else
          if(prev_holding==1 & signal=="H"){investment_return=Close/dal_df$Close[t-1]-1} else
            {investment_return = Open/dal_df$Close[t-1]-1}
 
   # save the investment return to the dataset
  dal_df$Inv_return[t] = investment_return}

Cumulative Return

Apple Stock

From the individual daily returns, we must create a cash and number of stocks vector to hold each days current figures.

Calculate and store in cash and n_stock vector:

Show code
# starting investment $100,000
initial_wealth = 100000
# create vectors to store updates
raw_data$cash = rep(initial_wealth, length(raw_data$Close))
raw_data$n_stock = rep(0, length(raw_data$Close))

# loop through signals and simulate the trades
for (t in 1:length(raw_data$signal)){
  
  if(t==1){prev_cash = initial_wealth; n_stock = 0
    
    } else
      
      {prev_cash = raw_data$cash[t-1]; 
      n_stock = raw_data$n_stock[t-1]}
  
  signal = raw_data$signal[t]
  Open = raw_data$Open[t]
  Close = raw_data$Close[t]
  High = raw_data$High[t]
  Low = raw_data$Low[t]
  
# is.na to deal with rows at  beginning with no signal
  
  if(is.na(signal)==TRUE){
    prev_cash = raw_data$cash[t-1]
    n_stock = raw_data$n_stock[t-1] 
    
    } else
      
      if (signal=="B"){
        buy_skid = (High-Open) * buy_weight + Open
        trans_prc = buy_skid
        n_change = floor(prev_cash/trans_prc)
        stock_n = n_stock + n_change
        cash = prev_cash - n_change * trans_prc
            
        raw_data$cash[t] = cash
        raw_data$n_stock[t] = stock_n
        
        } else
          
          if (signal =="S"){
            sell_skid = (Open-Low) * sell_weight + Open
            trans_prc = sell_skid
            n_change = n_stock
            stock_n = n_change - n_stock
            cash = prev_cash + n_change * trans_prc
        
            raw_data$cash[t] = cash
            raw_data$n_stock[t] = stock_n
            
            }else
              
              if (signal == "H") {
                raw_data$cash[t] = raw_data$cash[t-1]
                raw_data$n_stock[t] = raw_data$n_stock[t-1]
                
                }
}

DAL Stock

Show code
# starting investment $100,000
initial_wealth = 100000
# create vectors to store updates
dal_df$cash = rep(initial_wealth, length(dal_df$Close))
dal_df$n_stock = rep(0, length(dal_df$Close))

# loop through signals and simulate the trades
for (t in 1:length(dal_df$signal)){
  
  if(t==1){prev_cash = initial_wealth; n_stock = 0
    
    } else
      
      {prev_cash = dal_df$cash[t-1]; 
      n_stock = dal_df$n_stock[t-1]}
  
  signal = dal_df$signal[t]
  Open = dal_df$Open[t]
  Close = dal_df$Close[t]
  High = dal_df$High[t]
  Low = dal_df$Low[t]
  
# is.na to deal with rows at  beginning with no signal
  
  if(is.na(signal)==TRUE){
    prev_cash = dal_df$cash[t-1]
    n_stock = dal_df$n_stock[t-1] 
    
    } else
      
      if (signal=="B"){
        buy_skid = (High-Open) * buy_weight + Open
        trans_prc = buy_skid
        n_change = floor(prev_cash/trans_prc)
        stock_n = n_stock + n_change
        cash = prev_cash - n_change * trans_prc
            
        dal_df$cash[t] = cash
        dal_df$n_stock[t] = stock_n
        
        } else
          
          if (signal =="S"){
            sell_skid = (Open-Low) * sell_weight + Open
            trans_prc = sell_skid
            n_change = n_stock
            stock_n = n_change - n_stock
            cash = prev_cash + n_change * trans_prc
        
            dal_df$cash[t] = cash
            dal_df$n_stock[t] = stock_n
            
            }else
              
              if (signal == "H") {
                dal_df$cash[t] = dal_df$cash[t-1]
                dal_df$n_stock[t] = dal_df$n_stock[t-1]
                
                }
}

Final Return

Apple Stock

Calculate the cash plus the value of the stock on hand for the last day in the sample.

Print final return from trend following system and buy-and-hold strategy:

Show code
## Trend-following system
# Calculations for final portfolio value
final_return = raw_data$n_stock[max(index(raw_data))] * raw_data$Close[max(index(raw_data))] + raw_data$cash[max(index(raw_data))-1]

print(paste("Initial Investment:", sprintf("$ %3.2f", initial_wealth)))
[1] "Initial Investment: $ 100000.00"
Show code
print(paste("Trend-following system Final Return:",sprintf("$ %3.2f" , final_return)))
[1] "Trend-following system Final Return: $ 3017008.55"
Show code
## Buy and hold strategy
initial_price = raw_data$Open[1]
final_price = raw_data$Close[max(index(raw_data))]

n_purchased = floor(initial_wealth/initial_price)

total_return = (final_price - initial_price) * n_purchased
print(paste("Buy and hold system Final Return:",sprintf("$ %3.2f" , total_return)))
[1] "Buy and hold system Final Return: $ 4117924.89"

Delta Airlines Stock

Show code
## Trend-following system
# Calculations for final portfolio value
final_return_dal = dal_df$n_stock[max(index(dal_df))] * dal_df$Close[max(index(dal_df))] + dal_df$cash[max(index(dal_df))-1]

print(paste("Initial Investment:", sprintf("$ %3.2f", initial_wealth)))
[1] "Initial Investment: $ 100000.00"
Show code
print(paste("Trend-following system Final Return:",sprintf("$ %3.2f" , final_return_dal)))
[1] "Trend-following system Final Return: $ 70067.89"
Show code
## Buy and hold strategy
initial_price_dal = dal_df$Open[1]
final_price_dal = dal_df$Close[max(index(dal_df))]

n_purchased_dal = floor(initial_wealth/initial_price_dal)

total_return_dal = (final_price_dal - initial_price_dal) * n_purchased_dal
print(paste("Buy and hold system Final Return:",sprintf("$ %3.2f" , total_return_dal)))
[1] "Buy and hold system Final Return: $ 162001.84"

Visualize Days with Largest Loss and Gain

Show code
max_gain=raw_data[which.max(raw_data$Inv_return),]
max_loss = raw_data[which.min(raw_data$Inv_return),]

# max(raw_data$Inv_return, na.rm = TRUE)

# Create subset
max_data = raw_data[3310:3330,]
min_data = raw_data[3310:3330,]
max_loss
           Date    Open  High Low   Close    Volume    Price
3054 2020-03-16 60.4875 64.77  60 60.5525 322423600 59.13219
         return log_return  slow_ma  fast_ma  ema_diff max_range
3054 -0.1286471 -0.1377083 67.15978 71.94174 -4.781956    9.4925
          atr risk_per_lot MACD_direction signal holding Inv_return
3054 5.212188     26.06094     Decreasing      H       1  -0.128647
         cash n_stock
3054 16.21134   22389
Show code
max_gain
           Date    Open  High     Low   Close    Volume   Price
3053 2020-03-13 66.2225 69.98 63.2375 69.4925 370732000 67.8625
        return log_return  slow_ma  fast_ma ema_diff max_range
3053 0.1198083  0.1131576 67.24847 72.89084 -5.64237  7.922504
          atr risk_per_lot MACD_direction signal holding Inv_return
3053 4.774375     23.87188     Decreasing      H       1  0.1198083
         cash n_stock
3053 16.21134   22389
Show code
# Create arrow annotations

# Upper annotations
h_a <- list(
  x = max_gain$Date,
  y = max_gain$Price,
  text = "Largest Gain",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 2.5,
  opacity = 0.75,
  align = "right",
  ax = 5,
  ay = -55
)

# Lower annotations

l_a <- list(
  x = max_loss$Date,
  y = max_loss$Price,
  text = "Largest Loss",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 2.5,
  opacity = 0.75,
  align = "right",
  ax = -5,
  ay = 55
)


# figure labels
f <- list(
  family = "Courier New, monospace",
  size = 18,
  color = "#7f7f7f ")
x <- list(
  title = "x Axis",
  titlefont = f)
y <- list(
  title = "y Axis",
  titlefont = f)

Largest Gain

Show code
fig <- max_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)

# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))

# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)

# Add title
fig <- fig %>% layout(title = "AAPL Stock - Strategy Largest 1-Day Gain",
                       xaxis = list(rangeslider = list(visible = F)))

fig

Largest Loss

Show code
fig <- min_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)

# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))

# Add arrow annotations
fig <- fig %>% layout(annotations = l_a)

# Add title
fig <- fig %>% layout(title = "AAPL Stock - Strategy Largest 1-day Loss",
                       xaxis = list(rangeslider = list(visible = F)))

fig

Max Adverse & Favorable Single-Day Returns

Show code
max_daily_return = round((max(raw_data$Inv_return, na.rm = TRUE)*100),4)
max_daily_loss =  round((min(raw_data$Inv_return, na.rm = TRUE)*100), 4)
avg_daily_return = round((mean(raw_data$Inv_return, na.rm = TRUE)*100),4)

bh_max_return = round((max(raw_data$return, na.rm = TRUE)*100),4)
bh_max_loss =  round((min(raw_data$return, na.rm = TRUE)*100), 4)
bh_avg_return = round((mean(raw_data$return, na.rm = TRUE)*100),4)

print(paste("Largest 1-day return with trend system:", max_daily_return,"%"))
[1] "Largest 1-day return with trend system: 11.9808 %"
Show code
print(paste("Largest 1-day loss with trend system:", max_daily_loss,"%"))
[1] "Largest 1-day loss with trend system: -12.8647 %"
Show code
print(paste("Average 1-day with trend system return:", avg_daily_return,"%"))
[1] "Average 1-day with trend system return: 0.0995 %"
Show code
print(paste("Largest 1-day return buy and hold strategy:", bh_max_return,"%"))
[1] "Largest 1-day return buy and hold strategy: 13.905 %"
Show code
print(paste("Largest 1-day loss with buy and hold strategy:", bh_max_loss,"%"))
[1] "Largest 1-day loss with buy and hold strategy: -17.9195 %"
Show code
print(paste("Average 1-day with buy and hold strategy:", bh_avg_return,"%"))
[1] "Average 1-day with buy and hold strategy: 0.1169 %"

Performance Evaluation

Distribution of Returns

Apple Stock

Create a histogram of returns for a simple buy and hold strategy, as well as for our trend-following system:

Show code
p <- raw_data %>%
  ggplot(aes(x=Inv_return)) +
    geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
  coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 500)) +
  labs(x = "Investment Returns", y = "Frequency of Returns",
       title = "AAPL EMA Crossover - Histogram of Investment Returns",
       caption = "Trend following system - Slow lag: 150 periods, Fast lag: 25 periods")+
  theme_classic()
p
Show code
bh = raw_data %>%
  ggplot(aes(x=return)) +
    geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
  coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 500)) +
  labs(x = "Investment Returns", y = "Frequency of Returns",
       title = "AAPL Buy / Hold - Histogram of Investment Returns",
       caption = "This histogram considers a buy and hold strategy")+
  theme_classic()
bh
Show code
# ggsave("aapl_returns_hist.png", plot = p)
# ggsave("aapl_buy_hold_returns.png", plot = bh)

Delta Airlines Stock

Show code
p_dal <- dal_df %>%
  ggplot(aes(x=Inv_return)) +
    geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
  coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 280)) +
  labs(x = "Investment Returns", y = "Frequency of Returns",
       title = "DAL EMA Crossover - Histogram of Investment Returns",
       caption = "Trend following system - Slow lag: 150 periods, Fast lag: 25 periods")+
  theme_classic()
p_dal
Show code
# ggsave("dal_returns_hist.png", plot = p_dal)

Mean and Standard Deviation

AAPL

Retrieve the average daily return and volatility of our trend-following system, as well as a simple buy and hold strategy:

Show code
average_return = mean(raw_data$Inv_return, na.rm = TRUE) # average return
volatility = sd(raw_data$Inv_return, na.rm = TRUE) # volatility

print(paste("Average return:", round(average_return,6)))
[1] "Average return: 0.000995"
Show code
print(paste("Volatility:", round(volatility,6)))
[1] "Volatility: 0.014908"
Show code
bh_average_return = mean(raw_data$return, na.rm = TRUE) # average return
bh_volatility = sd(raw_data$return, na.rm = TRUE) # volatility

print(paste("Average return buy hold system:", round(bh_average_return,6)))
[1] "Average return buy hold system: 0.001169"
Show code
print(paste("Volatility buy hold system:", round(bh_volatility,6)))
[1] "Volatility buy hold system: 0.019597"
Show code
print(paste("Buy and hold strategy experiences larger volatility for a small amount of additional return"))
[1] "Buy and hold strategy experiences larger volatility for a small amount of additional return"

DAL

Show code
average_return_dal = mean(dal_df$Inv_return, na.rm = TRUE) # average return
volatility_dal = sd(dal_df$Inv_return, na.rm = TRUE) # volatility

print(paste("DAL Average return:", round(average_return_dal,6)))
[1] "DAL Average return: -5.8e-05"
Show code
print(paste("DAL Volatility:", round(volatility_dal,6)))
[1] "DAL Volatility: 0.018017"
Show code
print(paste("As we can see, the average return when using this system for DAL is much smaller and the volatility is larger. We can expect the Sharpe ratio to be much smaller in this environment"))
[1] "As we can see, the average return when using this system for DAL is much smaller and the volatility is larger. We can expect the Sharpe ratio to be much smaller in this environment"

Sharpe Ratio

Apple Stock

The Sharpe ratio was developed by Nobel laureate William F. Sharpe and is used to help investors understand the return of an investment compared to its risk.12 The ratio is the average return earned in excess of the risk-free rate per unit of volatility or total risk. Volatility is a measure of the price fluctuations of an asset or portfolio.

Show code
rf = .00001 # risk-free rate
print(paste("AAPL Trend following: Sharpe ratio of daily returns:",round((average_return-rf)/volatility,3)))
[1] "AAPL Trend following: Sharpe ratio of daily returns: 0.066"
Show code
print(paste("AAPL Buy & hold: Sharpe ratio of daily returns:",round((bh_average_return-rf)/bh_volatility,3)))
[1] "AAPL Buy & hold: Sharpe ratio of daily returns: 0.059"
Show code
print(paste("Our AAPL Trend-following system achieves a slightly higher Sharpe ratio"))
[1] "Our AAPL Trend-following system achieves a slightly higher Sharpe ratio"

Delta Stock

Show code
rf = .00001 # risk-free rate
print(paste("DAL Trend following: Sharpe ratio of daily returns:",round((average_return_dal-rf)/volatility_dal,5)))
[1] "DAL Trend following: Sharpe ratio of daily returns: -0.00377"
Show code
print(paste("Compared to Apple Trend-following returns, DAL's example provides a much smaller Sharpe ratio - this is due to whipsaw-like nature of Delta's historical stock price and the trend-following's lack of ability to trade in this type of environment (at least with the parameters we have chosen)"))
[1] "Compared to Apple Trend-following returns, DAL's example provides a much smaller Sharpe ratio - this is due to whipsaw-like nature of Delta's historical stock price and the trend-following's lack of ability to trade in this type of environment (at least with the parameters we have chosen)"

Value at Risk

Apple Stock

VAR measures the cut-off return that your financial asset will fall below with certain probability.

Value at risk (VaR) is a statistic that quantifies the extent of possible financial losses within a firm, portfolio, or position over a specific time frame. This metric is most commonly used by investmentand commercial banks to determine the extent and probabilities of potential losses in their institutional portfolios.

Show code
var = quantile(raw_data$Inv_return, 0.05, na.rm = TRUE)*100
bh_var = quantile(raw_data$return, 0.05, na.rm = TRUE)*100

print(paste("Apple Trend-following VAR: we can expect 95% of the time returns will be greater than:", round(var,4)))
[1] "Apple Trend-following VAR: we can expect 95% of the time returns will be greater than: -2.3029"
Show code
print(paste("Apple Buy / Hold model VAR: we can expect 95% of the time returns will be greater than:", round(bh_var,4)))
[1] "Apple Buy / Hold model VAR: we can expect 95% of the time returns will be greater than: -2.8991"

Delta Airlines

Show code
var_dal = quantile(dal_df$Inv_return, 0.05, na.rm = TRUE)*100


print(paste("DAL Trend-following VAR: we can expect 95% of the time returns will be greater than:", round(var_dal,4)))
[1] "DAL Trend-following VAR: we can expect 95% of the time returns will be greater than: -2.901"

Summary of Investment & Market Return

In order to break down risk into two components, we need to collect what part of the return was attributed to the market and what part to our trading system.

Fit CAPM Model

Show code
y = raw_data$Inv_return - rf
x = SPX$return - rf
Show code
# fit regression

capm = lm(y~x)
summary(capm)

Call:
lm(formula = y ~ x)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.084280 -0.006368 -0.000612  0.006285  0.099440 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.0007526  0.0002058   3.656 0.000259 ***
x           0.5856195  0.0159334  36.754  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.01285 on 3899 degrees of freedom
  (99 observations deleted due to missingness)
Multiple R-squared:  0.2573,    Adjusted R-squared:  0.2571 
F-statistic:  1351 on 1 and 3899 DF,  p-value: < 2.2e-16
Show code
print(paste("Trend-following system Beta:", round(capm$coefficients[2],4)))
[1] "Trend-following system Beta: 0.5856"

The larger the \(BETA\) is, the larger the systematic risk is.

We use this to measure the aggressiveness of the stock or the trading system.

Our trend-following system is mostly conservative.

Jensen’s Alpha (Abnormal Return)

Show code
tf_beta = capm$coefficients[2] # beta
jensens_alpha = capm$coefficients[1] # Jensen's Alpha

print(paste("Jensen's Alpha:", round(jensens_alpha,4)))
[1] "Jensen's Alpha: 8e-04"

Treynor’s Ratio

Show code
print(paste("Treynor's Ratio Version 1:", round(jensens_alpha/tf_beta, 4)))
[1] "Treynor's Ratio Version 1: 0.0013"
Show code
print(paste("Treynor's Ratio Version 2:", round(((mean(raw_data$Inv_return, na.rm = TRUE))/tf_beta),4)))
[1] "Treynor's Ratio Version 2: 0.0017"

Historical Portfolio Return

Show code
raw_data$portfolio_value = raw_data$n_stock*raw_data$Close + raw_data$cash
Show code
# Create arrow annotations for trade entry signal

# AAPL
# Upper annotations
h_a <- list(
  x = h$Date,
  y = h$slow_ma,
  text = "Sell",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "red",
  arrowhead = 5,
  arrowsize = 1,
  arrowwidth = 1.7,
  opacity = 0.75,
  align = "left",
  ax = 5,
  ay = -55
)

# Lower annotations

l_a <- list(
  x = l$Date,
  y = l$slow_ma,
  text = "Buy",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 1,
  arrowwidth = 1.7,
  opacity = 0.75,
  align = "right",
  ax = -5,
  ay = 55
)


# figure labels
f <- list(
  family = "Courier New, monospace",
  size = 24,
  color = "#7f7f7f ")
x <- list(
  title = "x Axis",
  titlefont = f)
y <- list(
  title = "y Axis",
  titlefont = f)
Show code
# Plot main data
fig <- raw_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow EMA",
            line = list(color = '#ccc', width = 0.75),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast EMA",
            line = list(color = '#E377C2', width = 0.75),
            legendgroup = "bands",
            hoverinfo = "none", inherit = F)

ay <- list(
  tickfont = list(color = "grey"),
  overlaying = "y",
  side = "right",
  title = "<b>Secondary:</b> Portfolio Value")

fig <- fig %>% add_lines(x = ~Date, y = ~portfolio_value, name = "Portfolio Value",
            line = list(color =  'limegreen', width = 1), dash = 'dot', yaxis = "y2")

# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))


# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)
fig <- fig %>% layout(annotations = l_a)



# Add title
fig <- fig %>% layout(
  title = "<b>Trend-Following System:</b> Portfolio Value", yaxis2 = ay,
  xaxis = list(rangeslider = list(visible = F)),
  yaxis = list(title = "<b>Primary:</b> Stock Price")
  ) %>%
  layout(plot_bgcolor = 'rgb(255, 255, 255)',
         xaxis = list(
            zerolinecolor = '#ffffff',
            zerolinewidth = 2,
            gridcolor = '#ffffff'),
          yaxis = list(
            zerolinecolor = '#ffffff',
            zerolinewidth = 2,
            gridcolor = '#ffffff')
          )


fig